/*
 * ksm - a really simple and fast x64 hypervisor
 * Copyright (C) 2016, 2017 Ahmed Samy <asamy@protonmail.com>
 *
 * Assembly stuff for GCC (AT&T aka GAS) only.  See vmx.asm for MASM.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __ASSEMBLY__
#error __ASSEMBLY__ is not defined...
#endif

#include "compiler.h"

#ifdef __linux__
/* Arguments  */
#define REG_A1		%rdi
#define REG_A2		%rsi
#define REG_A3		%rdx
#define REG_A4		%rcx
/* Volatile registers (caller-saved)  */
#define REG_V1		%rdx
#define REG_V2		%rcx
#define REG_V3		%rax
#define REG_V4		%r8
#define REG_V5		%r9
#define REG_V6		%r10
#define REG_V7		%r11
#define REG_V8		%rdi
#define REG_V9		%rsi
#else
/* Arguments  */
#define REG_A1		%rcx
#define REG_A2		%rdx
#define REG_A3		%r8
#define REG_A4		%r9
/* Volatile registers (caller-saved)  */
#define REG_V1		%rax
#define REG_V2		%rcx
#define REG_V3		%rdx
#define REG_V4		%r8
#define REG_V5		%r9
#define REG_V6		%r10
#define REG_V7		%r11
#endif

.macro PUSH_REGS
	pushq	%r15
	pushq	%r14
	pushq	%r13
	pushq	%r12
	pushq	%r11
	pushq	%r10
	pushq	%r9
	pushq	%r8
	pushq	%rdi
	pushq	%rsi
	pushq	%rbp
	subq	$8, %rsp	/* placeholder  */
	pushq	%rbx
	pushq	%rdx
	pushq	%rcx
	pushq	%rax
.endm

.macro POP_REGS
	popq	%rax
	popq	%rcx
	popq	%rdx
	popq	%rbx
	addq	$8, %rsp
	popq	%rbp
	popq	%rsi
	popq	%rdi
	popq	%r8
	popq	%r9
	popq	%r10
	popq	%r11
	popq	%r12
	popq	%r13
	popq	%r14
	popq	%r15
.endm

#define KFRAME_RPL	-0x66
#define KFRAME_CSR	-0x64
#define KFRAME_V1	-0x60
#define KFRAME_V2	-0x58
#define KFRAME_V3	-0x50
#define KFRAME_V4	-0x48
#define KFRAME_V5	-0x40
#define KFRAME_V6	-0x38
#define KFRAME_V7	-0x30
#define KFRAME_V8	-0x28
#define KFRAME_V9	-0x20
#define KFRAME_XMM0	-0x10
#define KFRAME_XMM1	0x00
#define KFRAME_XMM2	0x10
#define KFRAME_XMM3	0x20
#define KFRAME_XMM4	0x30
#define KFRAME_XMM5	0x40
#define KFRAME_SS	0x108
#define KFRAME_RSP	0x100
#define KFRAME_FLGS	0x0F8
#define KFRAME_CS	0x0F0
#define KFRAME_EC	0x0E0
#define KFRAME_IP	0x0E8

.macro TRAP_SAVE_GPR
	pushq	%rbp
	subq	$0x158, %rsp
	leaq	0x80(%rsp), %rbp
	movq	REG_V1, KFRAME_V1(%rbp)
	movq	REG_V2, KFRAME_V2(%rbp)
	movq	REG_V3, KFRAME_V3(%rbp)
	movq	REG_V4, KFRAME_V4(%rbp)
	movq	REG_V5, KFRAME_V5(%rbp)
	movq	REG_V6, KFRAME_V6(%rbp)
	movq	REG_V7, KFRAME_V7(%rbp)
#ifdef REG_V8
	movq	REG_V8, KFRAME_V8(%rbp)
#endif
#ifdef REG_V9
	movq	REG_V9, KFRAME_V9(%rbp)
#endif
.endm

.macro TRAP_REST_GPR
#ifdef REG_V9
	movq	KFRAME_V9(%rbp), REG_V9
#endif
#ifdef REG_V8
	movq	KFRAME_V8(%rbp), REG_V8
#endif
	movq	KFRAME_V7(%rbp), REG_V7
	movq	KFRAME_V6(%rbp), REG_V6
	movq	KFRAME_V5(%rbp), REG_V5
	movq	KFRAME_V4(%rbp), REG_V4
	movq	KFRAME_V3(%rbp), REG_V3
	movq	KFRAME_V2(%rbp), REG_V2
	movq	KFRAME_V1(%rbp), REG_V1
	movq	%rbp, %rsp
	movq	0xD8(%rbp), %rbp
	addq	$0xE8, %rsp
.endm

.macro TRAP_SAVE_XMM
#ifndef __linux__
	stmxcsr KFRAME_CSR(%rbp)
	ldmxcsr %gs:0x180
	movaps	%xmm0, KFRAME_XMM0(%rbp)
	movaps	%xmm1, KFRAME_XMM1(%rbp)
	movaps	%xmm2, KFRAME_XMM2(%rbp)
	movaps	%xmm3, KFRAME_XMM3(%rbp)
	movaps	%xmm4, KFRAME_XMM4(%rbp)
	movaps	%xmm5, KFRAME_XMM5(%rbp)
#endif
.endm

.macro TRAP_REST_XMM
#ifndef __linux__
	ldmxcsr KFRAME_CSR(%rbp)
	movaps KFRAME_XMM0(%rbp), %xmm0
	movaps KFRAME_XMM1(%rbp), %xmm1
	movaps KFRAME_XMM2(%rbp), %xmm2
	movaps KFRAME_XMM3(%rbp), %xmm3
	movaps KFRAME_XMM4(%rbp), %xmm4
	movaps KFRAME_XMM5(%rbp), %xmm5
#endif
.endm

.macro TRAP_ENTER
	TRAP_SAVE_GPR
	movw	KFRAME_CS(%rbp), %ax
	andb	$1, %al
	movb	%al, KFRAME_RPL(%rbp)
	jz	1f
	swapgs
1:
	cld
	TRAP_SAVE_XMM
.endm

.macro TRAP_EXIT
	testb	$1, KFRAME_RPL(%rbp)
	jz	1f

	TRAP_REST_XMM
	TRAP_REST_GPR
	swapgs
	iretq

1:
	TRAP_REST_XMM
	TRAP_REST_GPR
	iretq
.endm

.macro irq_entry symbol:req handler:req no_error_code:req
.globl \symbol
\symbol:
	.if \no_error_code
		subq	$8, %rsp
	.endif
	TRAP_ENTER
	movq	KFRAME_CS(%rbp), REG_A1
	movq	KFRAME_IP(%rbp), REG_A2
	subq	$0x20, %rsp
	call	\handler
	addq	$0x20, %rsp
	TRAP_EXIT
.endm

	.text

/* EPT violations  */
irq_entry __ept_violation	__ept_handle_violation	1

.globl __vmx_vminit
__vmx_vminit:
	pushfq
	PUSH_REGS

	/* parameter one is pass-through (vcpu).  */
	movq	%rsp, REG_A2
	movabs	$do_resume, REG_A3

	subq	$0x20, %rsp
	call	vcpu_run
	addq	$0x20, %rsp

	/* If we get here, we failed  */
	POP_REGS
	popfq

	movl	ERR_DENIED, %eax
	ret

do_resume:
	/* Succeeded  */
	POP_REGS
	popfq

	xorl 	%eax, %eax
	ret

.globl __vmx_entrypoint
__vmx_entrypoint:
	/*
	 * Host entry point (aka VMX root mode).
	 * Note: all interrupts are disabled at this point.
	 *
	 * Save all guest general purpose registers, then let the C handler do
	 * the rest.
	 *
	 *	This is how the stack looks like after PUSH_REGS:
	 *		+0   = %rax
	 *		+8   = %rcx
	 *		+16  = %rdx
	 *		+24  = %rbx
	 *		+32  = %rsp	(garbage, overwritten with real value in
	 *				 vcpu_handle_exit())
	 *		+40  = %rbp
	 *		+48  = %rsi
	 *		+56  = %rdi
	 *		+64  = %r8
	 *		+72  = %r9
	 *		+80  = %r10
	 *		+88  = %r11
	 *		+96  = %r12
	 *		+104 = %r13
	 *		+112 = %r14
	 *		+120 = %r15
	 *		+128 = vcpu (set in vcpu_create())
	 *
	 * Note: we pass vcpu->stack -8 to HOST_RSP, this is because
	 * at this specific area, it's set to the vcpu pointer.  So basically,
	 * a pop here (before PUSH_REGS) will pop vcpu pointer, but the
	 * registers in this specific context are guest registers, we need
	 * their values as-is untouched, we may also modify them later on
	 * depending on the event (e.g. cpuid, etc.)
	 */
	PUSH_REGS
	movq	%rsp, REG_A1

	subq	$0x20, %rsp
	call	vcpu_handle_exit
	addq	$0x20, %rsp

	testb	%al, %al
	jz	1f

	POP_REGS
	vmresume

	/* Something went wrong during re-entry to guest...  */
	jmp	2f

1:
	/* See exit.c (vcpu_do_exit)  */
	POP_REGS
	vmxoff
	jna	2f

	/* Give them their stack pointer  */
	movq	%rdx, %rsp

	/* and their rflags (adjusted to indicate successful vmcall)  */
	pushq	%rax
	popfq

	/*
	 * rcx contains return address, which is guest's rip + instr_len (aka
	 * VMCALL length)
	 */
	pushq	%rcx
	ret

2:
	/* Either vmresume or vmxoff failure...  */
	nop
	pushfq
	PUSH_REGS
	movq	%rsp, REG_A1
	subq	$0x20, %rsp
	call	vcpu_handle_fail
	addq	$0x20, %rsp


3:	/* shouldn't come here, but incase it does, hlt forever:  */
	hlt
	jmp 3b

